package fr.insalyon.sims.broadcast.service;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;

import fr.insalyon.sims.broadcast.image.ImageOutputBuffer;
import fr.insalyon.sims.broadcast.network.DataErrorEvent;
import fr.insalyon.sims.broadcast.network.DataErrorListener;
import fr.insalyon.sims.broadcast.network.MCastStreamReceiver;
import fr.insalyon.sims.broadcast.network.TCPStreamTransmitter;
import fr.insalyon.sims.broadcast.tools.InetTools;
import fr.insalyon.sims.streamManagerClient.Response;
import fr.insalyon.sims.streamManagerClient.Service;

public class BroadcastReceiverService extends BroadcastService
		implements DataErrorListener {
	// ------------------------------------------------------- CONSTANTS -- //
	// ------------------------------------------------------ ATTRIBUTES -- //
	
	// Input multicast stream
	private MCastStreamReceiver receiver;
	
	// Output TCP/IP stream
	private TCPStreamTransmitter transmitter;
	
	// ---------------------------------------------------- CONSTRUCTORS -- //
	
	public BroadcastReceiverService() {
		// No parameters to initialize
	}
	
	// --------------------------------------------------------- METHODS -- //
	
	public synchronized Response doStart(HashMap<String, String> params) {
		// Read multicast address & port
		InetAddress multicastAddress = null;
		int multicastPort = -1;
		
		// Read port value
		if (params.get("multicastPort") != null)
			multicastPort = InetTools.stringToPort(params.get("multicastPort"));
		
		// Check port is valid
		if (multicastPort == -1)
			return getErrorResponse(123,"Invalid or no multicast port value " +
					"(expected parameter: 'multicastPort').");
		
		// Read multicast group IP
		if (params.get("multicastIp") != null) {
			byte[] multicastIp = InetTools.stringToIp(params.get("multicastIp"));
			try {
				multicastAddress = InetAddress.getByAddress(multicastIp);
			} catch (UnknownHostException e) {
				// Invalid address
			}
		}
		
		// Check multicast group IP is valid
		if (multicastAddress == null)
			return getErrorResponse(123,"Invalid or no multicast address value " +
					"(expected parameter: 'multicastIp').");
		
		// Read ouput component address & port
		InetAddress destAddress = null;
		int destPort = -1;
		
		// Read port value
		if (params.get("destPort") != null)
			destPort = InetTools.stringToPort(params.get("destPort"));
		
		// Check port is valid
		if (destPort == -1)
			return getErrorResponse(120,"Invalid or no destination port value " +
					"(expected parameter: 'destPort').");
		
		// Read multicast group IP
		if (params.get("destIp") != null) {
			byte[] destIp = InetTools.stringToIp(params.get("destIp"));
			try {
				destAddress = InetAddress.getByAddress(destIp);
			} catch (UnknownHostException e) {
				// Invalid address
			}
		}
		
		// Check multicast group IP is valid
		if (destAddress == null)
			return getErrorResponse(123,"Invalid or no destination adress value " +
					"(expected parameter: 'multicastIp').");
		
		// Create and connect TCP/IP output stream
		try {
			this.transmitter = new TCPStreamTransmitter(destAddress, destPort);
			this.transmitter.connect();
		} catch (IOException e) {
			this.transmitter = null;
			return getErrorResponse(111,"Could not start TCP output " +
					"stream. Exception: " + e.getMessage());
		}
		
		// Create and connect input multicast stream
		try {
			this.receiver = new MCastStreamReceiver(
					multicastAddress, multicastPort);
			this.receiver.connect();
		} catch (Exception e) {
			this.transmitter.disconnect();
			this.transmitter = null;
			this.receiver = null;
			return getErrorResponse(112,"Could not start multicast input " +
					"stream. Exception: " + e.getMessage());
		}
		
		// Add listeners
		this.receiver.addDataListener(new ImageOutputBuffer(transmitter));
		this.receiver.addDataErrorListener(this);
		this.transmitter.addDataErrorListener(this);
		
		// Starts daemons
		this.receiver.start();
		this.transmitter.start();
		
		return getSuccessResponse(0); // FIXME n° de port inutile - a renvoyer quand meme ?
	}
	
	public synchronized Response doStop() {
		if (this.receiver != null) {
			// Stop transmitter
			this.transmitter.stop();
			this.transmitter.disconnect();
			
			// Stop receiver
			this.receiver.stop();
			try {
				this.receiver.disconnect();
			} catch (IOException e) {
				// Ignored
			}
			
			this.receiver = null;
			this.transmitter = null;
		}
		
		return getSuccessResponse();
	}
	
	public String getName() {
		return "BroadcastReceiverService";
	}
	
	public int getServiceType() {
		return Service.MULTICAST;
	}
	
	public void streamError(DataErrorEvent event) {
		// TODO signaler l'erreur au StreamManager
		System.err.println("[" + getName() + "] An exception occured!");
		event.getException().printStackTrace();
		doStop();
	}
	
	// ------------------------------------------------- PRIVATE METHODS -- //
	// --------------------------------------------------- INNER CLASSES -- //
}
